package com.xcity.game.rank;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import org.slf4j.Logger;

import com.xcity.Server;
import com.xcity.db.entity.EntityState;
import com.xcity.db.entity.Player;
import com.xcity.db.entity.Ranking;
import com.xcity.db.mapper.RankingMapper;
import com.xcity.game.event.GameEvents;
import com.xcity.game.player.PlayerService;
import com.xcity.util.DBUtils;

import gnu.trove.map.TLongObjectMap;
import gnu.trove.map.hash.TLongObjectHashMap;
import naga.x.App;
import naga.x.event.Event;
import naga.x.util.CollectionProcedure;

public abstract class BaseRankingList implements RankingList {

	protected byte type;
	protected int capacity;
	protected List<Ranking> entries;
	protected TLongObjectMap<Ranking> entryLookup; // key: player id
	protected boolean dirty;
	protected Comparator<Ranking> comparator;
	protected Lock lock;
	
	public BaseRankingList(int type, int capacity, Comparator<Ranking> comparator) {
		this.type = (byte) type;
		this.capacity = capacity;
		this.entries = new ArrayList<Ranking>(capacity);
		this.entryLookup = new TLongObjectHashMap<Ranking>(capacity >> 1);
		this.dirty = true; // force sort in the first time
		this.comparator = comparator;
		this.lock = new ReentrantLock();
	}
	
	@Override
	public int[] getEventTypes() {
		return null;
	}

	@Override
	public void handleEvent(Event event) {
	}
	
	@Override
	public void init() {
		List<Ranking> list = DBUtils.findLimit(RankingMapper.class, 0, getCapacity(), DBUtils.wrapParams("type", getType()));
		if (list != null) {
			addAll(list);
		}
	}

	@Override
	public byte getType() {
		return type;
	}

	@Override
	public int getCapacity() {
		return capacity;
	}

	@Override
	public int size() {
		final Lock lock = this.lock;
		lock.lock();
		try {
			return entries.size();
		} finally {
			lock.unlock();
		}
	}

	@Override
	public void add(Ranking entry) {
		final Lock lock = this.lock;
		lock.lock();
		try {
			entry.fireValueChanged(entries.size());
			entries.add(entry);
			entryLookup.put(entry.getPlayerId(), entry);
		} finally {
			lock.unlock();
		}
	}

	@Override
	public void addAll(Collection<Ranking> entries) {
		if (entries == null || entries.isEmpty()) {
			return;
		}
		final Lock lock = this.lock;
		lock.lock();
		try {
			for (Ranking entry : entries) {
				entry.fireValueChanged(entries.size());
				this.entries.add(entry);
				this.entryLookup.put(entry.getPlayerId(), entry);
			}
		} finally {
			lock.unlock();
		}
	}

	@Override
	public void remove(Player player) {
		remove(player.getId());
	}

	@Override
	public void remove(long key) {
		final Lock lock = this.lock;
		lock.lock();
		try {
			Ranking entry = entryLookup.remove(key);
			if (entry != null) {
				entries.remove(entry);
			}
		} finally {
			lock.unlock();
		}
	}

	@Override
	public void clearAsync() {
		if (size() == 0) {
			return;
		}
		App.getApp().getThreadPool().execute(new Runnable() {
			@Override
			public void run() {
				clear();
			}
		});
	}
	
	@Override
	public void clear() {
		final Lock lock = BaseRankingList.this.lock;
		lock.lock();
		try {
			Logger LOG = Server.LOG;
			for (int i = 0, size = entries.size(); i < size; i++) {
				Ranking entry = entries.get(i);
				if (entry != null) {
					entry.setState(EntityState.DELETE);
					entry.save();
					LOG.info("[RANK CLEAR]ID[{}]PID[{}]TYPE[{}]VAL[{}]STATE[{}]",
							entry.getId(), entry.getPlayerId(), entry.getType(), entry.getValue(), entry.getState());
				}
			}
			entries.clear();
			entryLookup.clear();
		} finally {
			lock.unlock();
		}
	}

	@Override
	public void save() {
		Logger LOG = Server.LOG;
		final Lock lock = this.lock;
		lock.lock();
		try {
			final List<Ranking> entries = this.entries;
			for (int i = 0; i < entries.size(); i++) {
				try {
					Ranking entry = entries.get(i);
					entry.fireValueChanged();
					entry.save();
				} catch (Exception e) {
					LOG.error(e.getMessage(), e);
				}
			}
		} finally {
			lock.unlock();
		}
	}

	@Override
	public List<Ranking> getEntries() {
		return entries;
	}

	@Override
	public List<Ranking> getEntries(Player player) {
		return entries;
	}

	@Override
	public Ranking getEntry(int index) {
		if (index < 0) {
			return null;
		}
		final Lock lock = this.lock;
		lock.lock();
		try {
			return index < entries.size() ? entries.get(index) : null;
		} finally {
			lock.unlock();
		}
	}

	@Override
	public Ranking findEntry(Player player) {
		return findEntry(player.getId());
	}

	@Override
	public Ranking findEntry(long key) {
		final Lock lock = this.lock;
		lock.lock();
		try {
			return entryLookup.get(key);
		} finally {
			lock.unlock();
		}
	}

	@Override
	public boolean swap(Player player1, Player player2) {
		return false;
	}

	@Override
	public Ranking setDirty(Player player, long newValue) {
		final Lock lock = this.lock;
		lock.lock();
		try {
			Ranking entry = entryLookup.get(player.getId());
			if (entry == null) {
				if (entries.size() >= capacity) { // 排行榜已满需与最后一名比较
					Ranking last = entries.get(entries.size() -1);
					if (newValue < last.getBufferValue()) { // 低于最后一名则不能入榜
						return null;
					}
					// 高于最后一名则将其替换
					entryLookup.remove(last.getPlayerId());
					last.setPlayerId(player.getId());
					last.setValue(newValue);
					entryLookup.put(last.getPlayerId(), last);
					entry = last;
				} else { // 排行榜未满直接入榜
					entry = new Ranking(player, type, newValue);
					add(entry);
				}
			}
			
			entry.valueChanged(newValue); // 标记值改变
			boolean _dirty = false;
			int index = entry.getIndex() - 1;
			Ranking previous = index >= 0 ? entries.get(index) : null;
			if (previous != null && newValue >= previous.getBufferValue()) { // 值相同，时间不同也需要重新排序
				_dirty = true;
			}
			index = entry.getIndex() + 1;
			Ranking after = index < entries.size() ? entries.get(index) : null;
			if (after != null && newValue <= after.getBufferValue()) {
				_dirty = true;
			}
			if (_dirty) {
				this.dirty = true;
			} else {
				entry.fireValueChanged(); // 不需要重新排序，直接更新值
			}
			return entry;
		} finally {
			lock.unlock();
		}
	}

	@Override
	public void doSort() {
		final Lock lock = this.lock;
		lock.lock();
		try {
			if (dirty) {
				dirty = false;
				PlayerService ps = App.getApp().getServiceManager().get(PlayerService.class);
				Collections.sort(entries, comparator);
				for (int i = entries.size() - 1; i >= 0; i--) {
					Ranking entry = entries.get(i);
					long oldVal = entry.getValue();
					int oldIndex = entry.getIndex();
					entry.fireValueChanged(i);
					if (oldVal != entry.getValue() && entry.getIndex() < oldIndex) {
						// 名次上升
						Player player = ps.getOnlinePlayer(entry.getPlayerId());
						if (player != null) {
							App.getApp().getEventManager().addEvent(GameEvents.EVENT_RANKING_UPPED, player, entry.getIndex() + 1, type);
						}
					}
				}
			}
		} finally {
			lock.unlock();
		}
	}

	@Override
	public void forEach(CollectionProcedure<Ranking> procedure) {
		final Lock lock = this.lock;
		lock.lock();
		try {
			List<Ranking> list = entries;
			for (int i = 0, size = list.size(); i < size; i++) {
				Ranking r = list.get(i);
				if (!procedure.execute(r)) {
					break;
				}
			}
		} finally {
			lock.unlock();
		}
	}

}
